import { createNextAnimateFrameEvent, createProcessingFromAsyncFunc, extendClassProps, getDomEventCurrentTargetPos, getReactFuncs, Processing, react, reactArray, ReactivePropsToNativePropsAndChanged, ReactiveVariable, step } from "xbsj-base";
import { PointEditing } from "..";
import { ESCesiumViewer } from "@czmSrc/ESCesiumViewer";
import { GeoCoordinates } from "./GeoCoordinates";
import { computePickingInfoWithCoordinates, CoordinatesComputingPickingInfo, CurrentScale, fromCartographic, geoNeareastPointOnRhumbLine, pickHeightPosition, pickVirtualEarth, positionFromCartesian, positionToCartesian, toCartesian } from "@czmSrc/utils";
import { CzmPolyline, CzmPolylines } from "@czmSrc/index";
import { ESJVector3D, ESJVector4D, geoRhumbDestination, geoRhumbDistance, geoRhumbHeading } from "earthsdk3";
import * as Cesium from 'cesium';

type OpInfoType = {
    step: ReactiveVariable<boolean>,
    moved: ReactiveVariable<boolean>,
    constraintMode: ReactiveVariable<CoordinatesComputingPickingInfo['constraintMode']>,
    originPosition: ReactiveVariable<ESJVector3D>,
    originHeading: ReactiveVariable<number>,
    originDimensions: ReactiveVariable<ESJVector3D>,
    draggingStartPosition: ReactiveVariable<ESJVector3D>;//拖拽开始位置
    movingPosition: ReactiveVariable<ESJVector3D>;//移动位置,不一定与目标位置相同
    targetPosition: ReactiveVariable<ESJVector3D>;//目标位置
    targetHeading: ReactiveVariable<number>,// 目标朝向
}

export class TranslationEditing extends PointEditing {
    private _opInfo: OpInfoType = {
        step: this.ad(react(false)),
        moved: this.ad(react(false)),
        constraintMode: this.ad(react<CoordinatesComputingPickingInfo['constraintMode']>('none')),
        originPosition: this.disposeVar(reactArray<ESJVector3D>(PointEditing.defaults.position)),
        originHeading: this.disposeVar(react(0)),
        originDimensions: this.disposeVar(reactArray<ESJVector3D>([1, 1, 1])),
        draggingStartPosition: this.disposeVar(reactArray<ESJVector3D>([0, 0, 0])),
        movingPosition: this.disposeVar(reactArray<ESJVector3D>([0, 0, 0])),
        targetPosition: this.disposeVar(reactArray<ESJVector3D>([0, 0, 0])),
        targetHeading: this.disposeVar(react(0)),
    }
    static override defaults = {
        ...PointEditing.defaults,
        heading: 0 as number, // 偏航角，度为单位
        dimensions: [1000, 1000, 1000] as ESJVector3D, // Cartesian3} [dimensions] A {@link Cartesian3} Property specifying the length, width, and height of the box.
        xAxisColor: [1, 0, 0, 1] as ESJVector4D,
        yAxisColor: [0, 1, 0, 1] as ESJVector4D,
        zAxisColor: [0, 0, 1, 1] as ESJVector4D,
    }
    constructor(czmViewer: ESCesiumViewer) {
        super();
        if (!czmViewer.viewer) return;
        const dimensions = this.ad(reactArray<ESJVector3D>([1, 1, 1]));
        // 双击取消编辑
        this.ad(czmViewer.dblclickEvent.don(() => this.enabled = false))
        // 根据相机视角控制坐标轴缩放
        this.ad(new CurrentScale(czmViewer, this.positionReact, dimensions, this.axisPixelSize));
        {
            // 终点坐标赋值
            this.ad(this._opInfo.targetPosition.changed.don((newVal) => {
                if (this._opInfo.moved) {
                    const tempPos = [...newVal] as ESJVector3D;
                    czmViewer.editingHeightOffset && (tempPos[2] += czmViewer.editingHeightOffset);
                    this.position = tempPos;
                }
            }))
            // 终点朝向赋值
            this.ad(this._opInfo.targetHeading.changed.don((newVal) => this._opInfo.moved && (this.heading = newVal)))
            //终点和原点判断是否移动过
            const update = () => {
                if (this._opInfo.constraintMode.value === 'none') {
                    this._opInfo.moved.value = false;
                    return;
                }
                if (this._opInfo.originHeading.value !== this._opInfo.targetHeading.value) {
                    this._opInfo.moved.value = true;
                    return;
                }
                if (!this._opInfo.originPosition.value.every((e, i) => e === this._opInfo.targetPosition.value[i])) {
                    this._opInfo.moved.value = true;
                    return;
                }
            }
            update();
            this.dispose(this._opInfo.originHeading.changed.disposableOn(update));
            this.dispose(this._opInfo.originPosition.changed.disposableOn(update));
            this.dispose(this._opInfo.targetHeading.changed.disposableOn(update));
            this.dispose(this._opInfo.targetPosition.changed.disposableOn(update));
        }
        {
            // 绘制地图效果
            // 移动坐标架
            const mainCoordinates = this._createCoordinates(czmViewer);
            // 原点坐标架
            const originCoordinates = this._createOriginCoordinates(czmViewer);
            const grid = this._createGrid(czmViewer);
            const helpLine = this._createHelpLine(czmViewer);
            const circle = this._createCircle(czmViewer);
            {
                // 显隐控制
                const update = () => {
                    circle.show = grid.show = this.enabled && this.showCircle;
                    helpLine.show = this.enabled && this.showCircle && this._opInfo.moved.value;
                    mainCoordinates.show = this.enabled && this.showCoordinates;
                    originCoordinates.show = this.enabled && this.showCoordinates && this._opInfo.moved.value;
                }
                update();
                const event = this.ad(createNextAnimateFrameEvent(
                    this.enabledChanged,
                    this.showCircleChanged,
                    this.showCoordinatesChanged,
                    this._opInfo.moved.changed,
                ))
                this.ad(event.don(update));
            }
            {
                // 坐标架显隐控制
                const update = () => {
                    mainCoordinates.xAxisShow = originCoordinates.xAxisShow = this.xAxisShow;
                    mainCoordinates.yAxisShow = originCoordinates.yAxisShow = this.yAxisShow;
                    mainCoordinates.zAxisShow = originCoordinates.zAxisShow = this.zAxisShow;
                }
                update();
                const event = this.ad(createNextAnimateFrameEvent(
                    this.xAxisShowChanged,
                    this.yAxisShowChanged,
                    this.zAxisShowChanged,
                ))
                this.ad(event.don(update));
            }
            {
                // 颜色控制
                const update = () => {
                    grid.color = this._opInfo.constraintMode.value !== 'none' ? [1, 1, 0, .6] : [1, 1, 0, .3];
                    circle.color = this._opInfo.constraintMode.value === 'zAxis' ? [1, 1, 0, 0.99] : [1, 1, 0, .5];
                    setCoordinatesColor(mainCoordinates, this._opInfo.constraintMode.value, this._opInfo.step.value);
                }
                update();
                this.dispose(this._opInfo.constraintMode.changed.disposableOn(update));
            }
            /**
             * 通过鼠标移动事件，计算判断鼠标悬浮在那个轴，并高亮显示
             * 通过鼠标按下事件启动鼠标拖拽计算坐标轴移动
             * 通过鼠标松开事件结束鼠标拖拽计算坐标轴移动
             */
            const pickingXYProcessing = this._pickingXYProcessing(czmViewer);
            const pickingZProcessing = this._pickingZProcessing(czmViewer);
            const pickingZAxisProcessing = this._pickingZAxisProcessing(czmViewer);
            const mainProcessing = this.ad(createProcessingFromAsyncFunc(async cancelsManager => {
                do {
                    this._opInfo.step.value = true;
                    this._opInfo.constraintMode.value = 'none';
                    cancelsManager.disposer.dispose(() => {
                        this._opInfo.constraintMode.value = 'none';
                    })
                    // 1.监听鼠标移动事件，判断鼠标是在那条轴，修改编辑模式，高亮对应区域
                    await step(cancelsManager, async cancelsManager => {
                        // 只有enabled为true时才进行监听，否则直接取消监听，
                        // enabled为false,地图效果都为false，没必要监听
                        // 绑定事件,进行初始化
                        const updatePosition = () => {
                            const tempPos = [...(this.position ?? TranslationEditing.defaults.position)] as ESJVector3D;
                            czmViewer.editingHeightOffset && (tempPos[2] -= czmViewer.editingHeightOffset);
                            this._opInfo.targetPosition.value = this._opInfo.originPosition.value = tempPos;
                        }
                        const updateHeading = () => this._opInfo.targetHeading.value = this._opInfo.originHeading.value = this.heading;
                        const updateDimensions = () => this._opInfo.originDimensions.value = dimensions.value;
                        cancelsManager.disposer.dispose((updatePosition(), this.positionChanged.don(updatePosition)));
                        cancelsManager.disposer.dispose((updateHeading(), this.headingChanged.don(updateHeading)));
                        cancelsManager.disposer.dispose((updateDimensions(), dimensions.changed.don(updateDimensions)));

                        const pickingInfo: CoordinatesComputingPickingInfo = {
                            constraintMode: 'none',
                            startDragPos: [0, 0, 0]
                        }
                        const updatePickingInfo = (pointerEvent: PointerEvent) => {
                            const ci = {
                                position: mainCoordinates.position ?? TranslationEditing.defaults.position,
                                heading: mainCoordinates.heading ?? TranslationEditing.defaults.heading,
                                dimensions: mainCoordinates.dimensions ?? TranslationEditing.defaults.dimensions,
                            }
                            if (czmViewer.viewer)
                                computePickingInfoWithCoordinates(
                                    pointerEvent,
                                    czmViewer.viewer.scene,
                                    ci,
                                    this.axisSnapPixelSize,
                                    pickingInfo,
                                    {
                                        x: this.disableX,
                                        y: this.disableY,
                                        z: this.disableZ,
                                        xy: this.disableXY,
                                        zAxis: this.disableZAxis,
                                    },
                                );
                            this._opInfo.constraintMode.value = pickingInfo.constraintMode;
                            this._opInfo.movingPosition.value = this._opInfo.draggingStartPosition.value = pickingInfo.startDragPos;
                        }
                        cancelsManager.disposer.dispose(czmViewer.pointerMoveEvent.don(pointerEvent => {
                            pointerEvent.pointerEvent && updatePickingInfo(pointerEvent.pointerEvent);
                        }))
                        // 异步等待，鼠标左键落下时且为编辑状态，完成此阶段，开始移动
                        await step(cancelsManager, cancelsManager => {
                            return new Promise<void>((resolve, reject) => {
                                cancelsManager.disposer.dispose(reject);
                                cancelsManager.disposer.dispose(czmViewer.pointerDownEvent.don(pointerEvent => {
                                    pointerEvent.pointerEvent && updatePickingInfo(pointerEvent.pointerEvent);
                                    if (pointerEvent.pointerEvent && pointerEvent.pointerEvent.button === 0
                                        && pickingInfo.constraintMode !== 'none') {
                                        resolve();
                                    }
                                }))
                            })
                        })
                    });
                    // 执行到此，说明鼠标拾取到坐标架范围，并且左键按下，开始进行鼠标操作
                    // 根据不同的约束模式，进行不同的操作，同时异步等待，鼠标左键抬起时，完成此阶段
                    this._opInfo.step.value = false;
                    await step(cancelsManager, async cancelsManager => {
                        {
                            // 阻止地图交互，防止鼠标移动，拖拽地球
                            czmViewer.incrementDisabledInputStack();
                            cancelsManager.disposer.dispose(() => {
                                czmViewer.decrementDisabledInputStack();
                            })
                        }
                        if (['xy', 'x', 'y'].includes(this._opInfo.constraintMode.value)) {
                            const inverseHeading = geoRhumbHeading(this._opInfo.draggingStartPosition.value, this._opInfo.originPosition.value);
                            const inverseDistance = geoRhumbDistance(this._opInfo.draggingStartPosition.value, this._opInfo.originPosition.value);
                            pickingXYProcessing.restart(undefined, inverseHeading, inverseDistance);
                            cancelsManager.disposer.dispose(() => pickingXYProcessing.cancel());
                        } else if (this._opInfo.constraintMode.value === 'z') {
                            pickingZProcessing.restart();
                            cancelsManager.disposer.dispose(() => pickingZProcessing.cancel());
                        } else if (this._opInfo.constraintMode.value === 'zAxis') {
                            pickingZAxisProcessing.restart();
                            cancelsManager.disposer.dispose(() => pickingZAxisProcessing.cancel());
                        } else {
                            console.warn(`pickingInfo.constraintMode error! ${this._opInfo.constraintMode.value}`);
                        }
                        await step(cancelsManager, cancelsManager => {
                            return new Promise<void>((resolve, reject) => {
                                cancelsManager.disposer.dispose(reject);
                                cancelsManager.disposer.dispose(czmViewer.pointerUpEvent.don(() => { resolve() }));
                                cancelsManager.disposer.dispose(czmViewer.pointerOutEvent.don(() => resolve()));
                            })
                        })
                    })
                } while (true);
            }))
            const update = () => {
                if (this.enabled) {
                    mainProcessing.restart();
                } else {
                    mainProcessing.isRunning && mainProcessing.cancel();
                }
            }
            update()
            this.ad(this.enabledChanged.don(update))
        }
    }
    private _createCoordinates = (czmViewer: ESCesiumViewer) => {
        const coordinates = this.ad(new GeoCoordinates(czmViewer));
        this.dispose(this._opInfo.step.changed.disposableOn(() => {
            setCoordinatesColor(coordinates, this._opInfo.constraintMode.value, this._opInfo.step.value)
        }));
        {
            const updateProp = () => {
                const position = this.position ?? TranslationEditing.defaults.position;
                const tempPos = [...position] as ESJVector3D;
                czmViewer.editingHeightOffset && (tempPos[2] -= czmViewer.editingHeightOffset);
                coordinates.position = tempPos;
            };
            updateProp();
            this.dispose(this.positionChanged.disposableOn(updateProp));
        }
        {
            const updateProp = () => {
                coordinates.heading = this.heading ?? 0;
            };
            updateProp();
            this.dispose(this.headingChanged.disposableOn(updateProp));
        }
        {
            const updateProp = () => {
                coordinates.dimensions = this._opInfo.originDimensions.value;
            };
            updateProp();
            this.dispose(this._opInfo.originDimensions.changed.disposableOn(updateProp));
        }
        return coordinates;
    }
    private _createOriginCoordinates = (czmViewer: ESCesiumViewer) => {
        const coordinates = this.ad(new GeoCoordinates(czmViewer));
        // 原点坐标架无需变色
        coordinates.xAxisColor = [1, 0, 0, 0.5];
        coordinates.yAxisColor = [0, 1, 0, 0.5];
        coordinates.zAxisColor = [0, 0, 1, 0.5];
        const { originPosition, originHeading, originDimensions } = this._opInfo;
        {
            const updateProp = () => {
                coordinates.position = originPosition.value;
            };
            updateProp();
            this.dispose(originPosition.changed.disposableOn(updateProp));
        }
        {
            const updateProp = () => {
                coordinates.heading = originHeading.value ?? 0;
            };
            updateProp();
            this.dispose(originHeading.changed.disposableOn(updateProp));
        }
        {
            const updateProp = () => {
                coordinates.dimensions = originDimensions.value;
            };
            updateProp();
            this.dispose(originDimensions.changed.disposableOn(updateProp));
        }
        return coordinates;
    }
    private _createGrid = (czmViewer: ESCesiumViewer) => {
        const grid = this.ad(new CzmPolylines(czmViewer));
        {
            const update = () => {
                const p = this._opInfo.originPosition.value;
                const h = this._opInfo.originHeading.value;
                const d = this._opInfo.originDimensions.value[0];
                const positionsSet: ESJVector3D[][] = [];
                for (let i = -4; i < 5; ++i) {
                    const c = geoRhumbDestination(p, d * 0.2 * i, h + 90);
                    if (c) {
                        const c0 = geoRhumbDestination(c, d, h + 180);
                        const c1 = geoRhumbDestination(c, d, h);
                        c0 && c1 && positionsSet.push([c0, c, c1]);
                    }
                }
                for (let i = -4; i < 5; ++i) {
                    const c = geoRhumbDestination(p, d * 0.2 * i, h);
                    if (c) {
                        const c0 = geoRhumbDestination(c, d, h - 90);
                        const c1 = geoRhumbDestination(c, d, h + 90);
                        c0 && c1 && positionsSet.push([c0, c, c1]);
                    }
                }
                grid.positions = positionsSet;
            };
            update();
            const event = this.disposeVar(createNextAnimateFrameEvent(
                this._opInfo.originPosition.changed,
                this._opInfo.originHeading.changed,
                this._opInfo.originDimensions.changed,
            ));
            this.dispose(event.disposableOn(update));
        }
        return grid;
    }
    private _createHelpLine = (czmViewer: ESCesiumViewer) => {
        const line = this.ad(new CzmPolyline(czmViewer));
        line.hasDash = true;
        line.arcType = 'RHUMB';
        line.color = [1, 1, 0, .99];
        {
            const update = () => {
                line.positions = [
                    this._opInfo.originPosition.value,
                    this._opInfo.targetPosition.value
                ]
            };
            const event = this.ad(createNextAnimateFrameEvent(
                this._opInfo.originPosition.changed,
                this._opInfo.targetPosition.changed
            ));
            this.ad(event.don(update));
        }
        return line;
    }
    private _createCircle = (czmViewer: ESCesiumViewer) => {
        const circle = this.ad(new CzmPolylines(czmViewer));
        circle.arcType = 'RHUMB';
        circle.width = 2;
        circle.hasDash = true;
        const event = this.disposeVar(createNextAnimateFrameEvent(
            this._opInfo.originPosition.changed,
            this._opInfo.originDimensions.changed
        ));
        const updatePosition = () => {
            // 根据缩放计算出的半径画圆
            const p = this._opInfo.originPosition.value;
            const d = this._opInfo.originDimensions.value[0];
            const positions: ESJVector3D[] = [];
            const sides = 32;
            for (let i = 0; i <= sides; ++i) {
                const c = geoRhumbDestination(p, d, i * 360 / sides);
                c && positions.push(c);
            }
            circle.positions = [positions];
        };
        updatePosition();
        this.dispose(event.disposableOn(updatePosition));
        return circle;
    }
    private _pickingXYProcessing = (czmViewer: ESCesiumViewer,) => {
        return this.ad(new Processing<void, [inverseHeading: number, inverseDistance: number]>((_, inverseHeading, inverseDistance) => {
            const movingPosition: ESJVector3D = [0, 0, 0];
            const movingCartographic = new Cesium.Cartographic();
            return czmViewer.pointerMoveEvent.don(pointerEvent => {
                if (!pointerEvent.pointerEvent || !czmViewer.viewer) return;
                const screenPosition = toCartesian(getDomEventCurrentTargetPos(pointerEvent.pointerEvent));
                if (!pickVirtualEarth(czmViewer.viewer.scene, screenPosition, this._opInfo.originPosition.value[2], movingCartographic)) return;
                fromCartographic(movingCartographic, movingPosition);
                if (this._opInfo.constraintMode.value === 'x') {
                    geoNeareastPointOnRhumbLine(this._opInfo.originPosition.value, this._opInfo.originHeading.value + 90, movingPosition, movingPosition);
                } else if (this._opInfo.constraintMode.value === 'y') {
                    geoNeareastPointOnRhumbLine(this._opInfo.originPosition.value, this._opInfo.originHeading.value, movingPosition, movingPosition);
                } else if (this._opInfo.constraintMode.value === 'xy') {

                } else {
                    console.warn(`should not be here!`);
                }
                this._opInfo.movingPosition.value = movingPosition;
                const stopPosition: ESJVector3D = [0, 0, 0];
                geoRhumbDestination(movingPosition, inverseDistance, inverseHeading, stopPosition);
                this._opInfo.targetPosition.value = stopPosition;
            })
        }))
    }
    private _pickingZProcessing = (czmViewer: ESCesiumViewer) => {
        return this.ad(new Processing(() => {
            const movingCartesian = new Cesium.Cartesian3();
            const movingPosition: ESJVector3D = [0, 0, 0];
            return czmViewer.pointerMoveEvent.don(pointerEvent => {
                if (!pointerEvent.pointerEvent || !czmViewer.viewer) return;
                const screenPosition = toCartesian(getDomEventCurrentTargetPos(pointerEvent.pointerEvent));
                if (!pickHeightPosition(czmViewer.viewer.scene, positionToCartesian(this._opInfo.originPosition.value), screenPosition, movingCartesian)) return;
                const diffHeight = this._opInfo.originPosition.value[2] - this._opInfo.draggingStartPosition.value[2];
                if (positionFromCartesian(movingCartesian, movingPosition)) {
                    this._opInfo.movingPosition.value = movingPosition;
                    const op = this._opInfo.originPosition.value;
                    this._opInfo.targetPosition.value = [op[0], op[1], movingPosition[2] + diffHeight];
                }
            })
        }))
    }
    private _pickingZAxisProcessing = (czmViewer: ESCesiumViewer) => {
        return this.ad(new Processing(() => {
            const movingPosition: ESJVector3D = [0, 0, 0];
            const movingCartographic = new Cesium.Cartographic();
            return czmViewer.pointerMoveEvent.don(pointerEvent => {
                if (!pointerEvent.pointerEvent || !czmViewer.viewer) return;
                const screenPosition = toCartesian(getDomEventCurrentTargetPos(pointerEvent.pointerEvent));
                if (!pickVirtualEarth(czmViewer.viewer.scene, screenPosition, this._opInfo.originPosition.value[2], movingCartographic)) return;
                fromCartographic(movingCartographic, movingPosition);
                const startAngle = geoRhumbHeading(this._opInfo.originPosition.value, this._opInfo.draggingStartPosition.value);
                const stopAngle = geoRhumbHeading(this._opInfo.originPosition.value, movingPosition);
                const diffAngle = stopAngle - startAngle;
                let destAngle = this._opInfo.originHeading.value + diffAngle;
                destAngle = Cesium.Math.toDegrees(Cesium.Math.negativePiToPi(Cesium.Math.toRadians(destAngle)));
                this._opInfo.movingPosition.value = movingPosition;
                this.heading = this._opInfo.targetHeading.value = destAngle;
            })
        }))
    }
}
export namespace TranslationEditing {
    export const createDefaultProps = () => ({
        ...PointEditing.createDefaultProps(),
        heading: 0,
        axisPixelSize: 100,
        axisSnapPixelSize: 5,
        showCoordinates: true,
        showCircle: true,
        disableX: false,
        disableY: false,
        disableZ: false,
        disableXY: false,
        disableZAxis: false,
        xAxisShow: true,
        yAxisShow: true,
        zAxisShow: true,
    });
}
extendClassProps(TranslationEditing.prototype, TranslationEditing.createDefaultProps);
export interface TranslationEditing extends ReactivePropsToNativePropsAndChanged<ReturnType<typeof TranslationEditing.createDefaultProps>> { }
function setCoordinatesColor(coordinates: GeoCoordinates, constraintMode: CoordinatesComputingPickingInfo['constraintMode'], transparent: boolean) {
    coordinates.xAxisColor = [1, 0, 0, .99];
    coordinates.yAxisColor = [0, 1, 0, .99];
    coordinates.zAxisColor = [0, 0, 1, .99];
    if (constraintMode === 'x') {
        coordinates.xAxisColor = transparent ? [1, 1, 0, .8] : [1, 1, 0, .99];
    } else if (constraintMode === 'y') {
        coordinates.yAxisColor = transparent ? [1, 1, 0, .8] : [1, 1, 0, .99];
    } else if (constraintMode === 'z') {
        coordinates.zAxisColor = transparent ? [1, 1, 0, .8] : [1, 1, 0, .99];
    } else if (constraintMode === 'xy') {
        coordinates.xAxisColor = transparent ? [1, 1, 0, .8] : [1, 1, 0, .99];
        coordinates.yAxisColor = transparent ? [1, 1, 0, .8] : [1, 1, 0, .99];
    }
}